package org.fhnw.aigs.server.gameHandling; import org.fhnw.aigs.server.common.Main; import org.fhnw.aigs.server.common.ServerConfiguration; import java.io.File; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.HashMap; import org.apache.tools.ant.*; import org.fhnw.aigs.server.common.LogRouter; import org.fhnw.aigs.server.common.LoggingLevel; /** * This class is responsible is part of the plug-in mechanism. A game logic * module (game) consists of two parts: Game commons and game server logic. If a * player wants to initialize those games, they must be present in the gamelibs * folder in the form of jars. This class provides a mechanism to retrieve those * jars and the content inside the archives. It does so by providing an * URLClassLoader for every game. When a new game is started loaded, the server * will first check whether there is already a URLClassLoader pointing to the * jar containing all the game classes. If that is not the case, a new class * loader will be created and added to the {@link GameLoader#allClassLoaders} * collection.<br> * v1.0 Initial release<br> * v1.1 Functional changes<br> * v1.2 Added new methods<br> * v1.3 Changing of logging * * @author Matthias Stöckli (v1.0) * @version 1.3 (Raphael Stoeckli, 24.02.2015) */ public class GameLoader extends URLClassLoader { /** * This field contains a reference to all ClassLoaders, and thus all classes * being part of a game. The key is a string, the game's name. The key is * provided by the client who requests to start a game. If the key is not * identical to the name of the jar (or at least a part of the name), the * game cannot be loaded. This is one of the most common mistakes. Therefore * it is adviced to ensure that the game is named consistently throught all * classes. */ private static HashMap<String, URLClassLoader> allClassLoaders = new HashMap<String, URLClassLoader>(); /** * This method loads a ClassLoader which can then be used to load a game. * * @param name The name/key to load a game's classes. * @return The URLClassLoader containing all classes of the given game. */ public static URLClassLoader getClassLoaderByName(String name) { // The mechanism is not case sensitive name = name.toLowerCase(); // Get the class loader, if there is no ClassLoader yet, create a new one. if (allClassLoaders.containsKey(name)) { return allClassLoaders.get(name); } else { URLClassLoader newClassLoader = createClassLoaderByName(name); allClassLoaders.put(name, newClassLoader); return newClassLoader; } } /** * Returns a list of all Games in the GameLibs directory * @return Arraylist with game names * @since v1.1 */ public static ArrayList<String> getInstalledGames() { ArrayList<String> games = new ArrayList<>(); String gamelibsDirectory = ServerConfiguration.getInstance().getGamelibsDirectory(); if (GameLoader.checkFolderExists(gamelibsDirectory, true) == false) { //LOG//Logger.getLogger(GameLoader.class.getName()).log(Level.SEVERE, "The gamelibs folder could not be created. Check the server installation!"); LogRouter.log(GameLoader.class.getName(), LoggingLevel.severe, "The gamelibs folder could not be created. Check the server installation!"); } File dir = new File(gamelibsDirectory); File[] files = dir.listFiles(); int pos; String name, ext; for (int i = 0; i < files.length; i++) { if (files[i].isFile() == true) { name = files[i].getName(); pos = name.lastIndexOf("."); if (pos > 0) { ext = name.substring(pos); name = name.substring(0,pos); if (ext.toLowerCase().equals(".jar")) // Only Jars { if (name.toLowerCase().endsWith("server") || name.toLowerCase().endsWith("commons") || name.toLowerCase().endsWith("sources") || name.toLowerCase().endsWith("client")) { continue; // Skip other libraries (only __GAME__.jar will be listed) } games.add(name); } } } } return games; } /** * Creastes a new ClassLoader using the game's name as a key. * * @param name The name/key to load a game's classes. * @return A new URLClassLoader containing a reference to all of the game's * classes. */ private static URLClassLoader createClassLoaderByName(String name) { URLClassLoader newClassLoader; // Load all jars in the gamelibs folder. // File jarFolder = new File("gamelibs"); // DEPRECATED String gamelibsDirectory = ServerConfiguration.getInstance().getGamelibsDirectory(); if (GameLoader.checkFolderExists(gamelibsDirectory, true) == false) { //LOG//Logger.getLogger(GameLoader.class.getName()).log(Level.SEVERE, "The gamelibs folder could not be created. Check the server installation!"); LogRouter.log(GameLoader.class.getName(), LoggingLevel.severe, "The gamelibs folder could not be created. Check the server installation!"); } File jarFolder = new File(gamelibsDirectory); File[] files = jarFolder.listFiles(); URL gameJarURL = null; URL commonsJarURL = null; URL aigsCommonsURL = null; /* * Go through all files. Then grab those files that end on "Server" * or are identical with the provided game's name. This prevents the * common mistake that a game logic module's name does not end on Server, * e.g. instead of "TicTacToeServer" the name of the module is just * "TicTacToe". Then add these files to a collection. Also load the * AIGS Commons into every collection as the modules can make use of * the commons too. */ try { for (File file : files) { String fileNameLC = file.getName().toLowerCase(); if (fileNameLC.equals(name + "server.jar") || fileNameLC.equals(name + ".jar")) { //gameJarURL = new URL("file:gamelibs/" + file.getName()); // DEPRECATED gameJarURL = new URL("file:" + gamelibsDirectory + "/" + file.getName()); } else if (fileNameLC.equals(name + "commons.jar")) { //commonsJarURL = new URL("file:gamelibs/" + file.getName()); // DEPRECATED commonsJarURL = new URL("file:" + gamelibsDirectory + "/" + file.getName()); } } aigsCommonsURL = new URL("file:lib/AIGS_Commons.jar"); } catch (MalformedURLException ex) { //LOG//Logger.getLogger(GameLoader.class.getName()).log(Level.SEVERE, "Could not load jar.", ex); LogRouter.log(GameLoader.class.getName(), LoggingLevel.severe, "Could not load jar.", ex); } catch (Exception ex) // All other exceptions { //LOG//Logger.getLogger(GameLoader.class.getName()).log(Level.SEVERE, "An unknown error occurred.", ex); LogRouter.log(GameLoader.class.getName(), LoggingLevel.severe, "An unknown error occurred.", ex); } // Create a new URLClassLoader using reflection (newInstance) URL[] urls = new URL[]{gameJarURL, commonsJarURL, aigsCommonsURL}; newClassLoader = URLClassLoader.newInstance(urls, Main.class.getClassLoader()); Class classLoaderClazz = URLClassLoader.class; Method addMethod = null; // Use reflection to get the hidden method "addURL" of the URLClassLoader // class. Then set the accessibility to true. try { addMethod = classLoaderClazz.getDeclaredMethod("addURL", new Class[]{URL.class}); addMethod.setAccessible(true); } catch (NoSuchMethodException ex) { //LOG//Logger.getLogger(GameLoader.class.getName()).log(Level.SEVERE, "The method 'addURL' does not exist.", ex); LogRouter.log(GameLoader.class.getName(), LoggingLevel.severe, "The method 'addURL' does not exist.", ex); } catch (SecurityException ex) { //LOG//Logger.getLogger(GameLoader.class.getName()).log(Level.SEVERE, "Security was violated.", ex); LogRouter.log(GameLoader.class.getName(), LoggingLevel.severe, "Security was violated.", ex); } catch (Exception ex) // All other exceptions { //LOG//Logger.getLogger(GameLoader.class.getName()).log(Level.SEVERE, "An unknown error occurred.", ex); LogRouter.log(GameLoader.class.getName(), LoggingLevel.severe, "An unknown error occurred.", ex); } // Add the URLs to the jars to the newly creasted URLClassLoader. try { addMethod.invoke(newClassLoader, gameJarURL); addMethod.invoke(newClassLoader, commonsJarURL); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { //LOG//Logger.getLogger(GameLoader.class.getName()).log(Level.SEVERE, "URL could not be added.", ex); LogRouter.log(GameLoader.class.getName(), LoggingLevel.severe, "URL could not be added.", ex); } catch (Exception ex) // All other exceptions { //LOG//Logger.getLogger(GameLoader.class.getName()).log(Level.SEVERE, "An unknown error occurred.", ex); LogRouter.log(GameLoader.class.getName(), LoggingLevel.severe, "An unknown error occurred.", ex); } return newClassLoader; } /** * Rebuilds all game jars using the ant build.xml. Also see: * http://stackoverflow.com/questions/6733684/run-ant-from-java Basically * run the target "rebuildGames" from the build.xml-File. * @return Returns true if compilation was successful, otherwise false */ public static boolean rebuildClasses() { boolean state = true; File buildFile = new File("build.xml"); Project p = new Project(); p.setUserProperty("ant.file", buildFile.getAbsolutePath()); p.init(); ProjectHelper helper = ProjectHelper.getProjectHelper(); p.addReference("ant.projectHelper", helper); helper.parse(p, buildFile); try { p.executeTarget("rebuildGames"); } catch (BuildException ex) { //LOG//Logger.getLogger(GameLoader.class.getName()).log(Level.SEVERE, "Could not load jar.", ex); LogRouter.log(GameLoader.class.getName(), LoggingLevel.severe, "Could not load jar.", ex); state = false; } allClassLoaders.clear(); return state; } /** * Not sure about the need of this method (v1.1) * @deprecated * @param urls * @param parent */ private GameLoader(URL[] urls, ClassLoader parent) { super(urls, parent); } /** * Checks the existence of a folder * @param path The path to be checked * @param createEmptyFolder If true, a empty directory will be created, if the folder does not exist * @since v1.2 * @return True, if the folder exists, false if not, respectively if the folder could not be created */ public static boolean checkFolderExists(String path, boolean createEmptyFolder) { try { File f = new File(path); if (f.exists()) { if (f.isDirectory() == true) { return true; } else { //LOG//Logger.getLogger(GameLoader.class.getName()).log(Level.SEVERE, "The defined path '" + path + "' is not a folder"); LogRouter.log(GameLoader.class.getName(), LoggingLevel.severe, "The defined path '" + path + "' is not a folder"); return false; // } } else { if (createEmptyFolder == true) { f.mkdir(); //LOG//Logger.getLogger(GameLoader.class.getName()).log(Level.INFO, "The folder '" + path + "' was created"); LogRouter.log(GameLoader.class.getName(), LoggingLevel.info, "The folder '" + path + "' was created"); return true; } else { //LOG//Logger.getLogger(GameLoader.class.getName()).log(Level.WARNING, "The folder '" + path + "' does not exist"); LogRouter.log(GameLoader.class.getName(), LoggingLevel.waring, "The folder '" + path + "' does not exist"); return false; } } } catch(Exception e) { //LOG//Logger.getLogger(GameLoader.class.getName()).log(Level.SEVERE, "There is a problem with the path '" + path + "' (does not exist)", e); LogRouter.log(GameLoader.class.getName(), LoggingLevel.severe, "There is a problem with the path '" + path + "' (does not exist)", e); return false; } } }